/**
**************************************************************************************
*Founder Effect                                                                      *
*Copyright (C) 2006-2007, 5d Studios (www.5d-Studios.com)                            *
*                                                                                    *
*This library is free software; you can redistribute it and/or                       *
*modify it under the terms of the GNU Lesser General Public                          *
*License as published by the Free Software Foundation; either                        *
*version 2.1 of the License, or (at your option) any later version.                  *
*                                                                                    *
*This library is distributed in the hope that it will be useful,                     *
*but WITHOUT ANY WARRANTY; without even the implied warranty of                      *
*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU                   *
*Lesser General Public License for more details.                                     *
*                                                                                    *
*You should have received a copy of the GNU Lesser General Public                    *
*License along with this library; if not, write to the Free Software                 *
*Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA      *
**************************************************************************************
*/
#include "StdAfx.h"
#include "AI_Brain.h"
#include "Scene.h"
#include "EntityManager.h"
#include "IEntity.h"
#include "PlayerConfig.h"
#include "../render/ICamera.h"
#include "../shared/QuadTree.h"


//extern Kernel* g_kernel;
//#define TIME g_kernel->GetTime()

// Tile Size
const int TILE_SIZE = 80;


/** Calculate the distance between two pathnodes */
float Distance(PathNode &v1, PathNode &v2)
{
    return v1.location.Distance( v2.location );
}

/** Test to see if there is an obsticle */
bool HasLineOfSight(const Vector2f &vect1, const Vector2f &vect2)
{
	Scene* s = EntityManager::GetInstance()->GetScene();
	
	Scene::type_Tiles tileList;
	s->RayTrace(vect1, vect2, tileList );

	return !tileList.empty();	 
}

/**-----------------------------------------------------------
    AI_Brain
--------------------------------------------------------------*/

//constructor
AI_Brain::AI_Brain(void)
{

}

//deconstructor
AI_Brain::~AI_Brain(void)
{
}

/** Determine if the entity can move here */
bool AI_Brain::LegalMove(IEntity* entPtr, Vector2f &testedVector)
{
    // TEMP!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    Scene* s = EntityManager::GetInstance()->GetScene();
    if ( s )
		return !s->Collide(testedVector.x,testedVector.y, entPtr->GetBounds() ) ; 
    return false;
}

void AI_Brain::Pause(IEntity* entPtr)
{
//	AI_Data &data = m_dataTable[entPtr];
//	data.pauseTime = TIME + data.pauseLength;
}

bool AI_Brain::IsPaused(IEntity* entity)
{
///	AI_Data &data = m_dataTable[entPtr];
	//if(data.pauseTime < TIME)
	//{
//		data.pauseTime = 0;
		return false;
//	}

}

/** Add a neighboring node */
void AI_Brain::AddNeighbor(std::list<PathNode> &openList, PathNode &entNode, IEntity* entPtr, const Vector2f destination, const Vector2f &offSet)
{
	bool isClosed = false;

	//move to where the entity would be if offset was added to its location
	Vector2f entLoc( entNode.location + offSet );
	if( LegalMove(entPtr, entLoc) )
	{
		//check if this position is already in the list
        std::list<PathNode>::iterator iter;
		for(iter = openList.begin(); iter != openList.end()&& !isClosed; iter++)
		{
			PathNode node = *iter;
			if( node.location == entLoc)
				isClosed = true;
		}
		//if this position hasnt been visited, add it to the queue
		if(!isClosed)
		{
			PathNode node( entLoc );
			node.entityPath = &entNode;
			node.distanceFromGoal = entLoc.Distance( destination );
			node.isClosed = false;
			openList.push_front(node);
		}
	}
}

/** Adds the neighbors of the current position into the open list if they are valid choices */
void AI_Brain::AddNeighbors(std::list<PathNode> &openList, PathNode &entNode, IEntity* entPtr, const Vector2f destination)
{
	//int eightDirectionsX[] = {-TILE_SIZE,   0,  TILE_SIZE, TILE_SIZE, TILE_SIZE,  0, -TILE_SIZE, -TILE_SIZE};
	//int eightDirectionsY[] = {-TILE_SIZE, -TILE_SIZE, -TILE_SIZE,  0, TILE_SIZE, TILE_SIZE,  TILE_SIZE,   0};
	const int MAX_DIRECTIONS = 4;

	int fourDirectionsX[] = {-TILE_SIZE,  0, TILE_SIZE,   0};
	int fourDirectionsY[] = {  0, TILE_SIZE,  0, -TILE_SIZE};

	//float dist = entPtr->m_physObject.GetPosition().Distance( destination );
	for(int i = 0; i < MAX_DIRECTIONS; ++i)
		//addNeighbor(openList, entNode, entPtr, playerPtr, Vector2f( eightDirectionsX[i], eightDirectionsY[i]));
		AddNeighbor(openList, entNode, entPtr, destination, Vector2f( fourDirectionsX[i], fourDirectionsY[i]));

}

/** Check to see if this entity is close enough to a destination */
bool AI_Brain::CloseEnough(const Vector2f &dest, IEntity* entPtr)
{
	return (abs( entPtr->m_physObject.GetPosition().Distance( dest ) ) < 8);
}

/** Check to see if an enemy is in your point of view */
bool AI_Brain::EnemyInView(int face, const Vector2f &playerLoc, const Vector2f &entLoc)
{
	if(face == NORTH)
		return (playerLoc.y < entLoc.y);

	if(face == SOUTH)
		return (playerLoc.y > entLoc.y);

	if(face == EAST)
		return (playerLoc.x > entLoc.x);

	if(face == WEST)
		return (playerLoc.x < entLoc.x);

	return false;
}

/** Add a Entity to this Brain */
void AI_Brain::AddEntity(IEntity* ptr)
{
	AI_Data data;
	
    // TODO - read patrol path from a .BOT file
	data.patrol.insert(data.patrol.begin(),Vector2f(200,100) );
	data.patrol.insert(data.patrol.begin(),Vector2f(200,400) );
	data.awareOfPlayer = false;
    data.inRoute = false;
	data.patrolIndex = 0;
	data.patrolVelocity = Vector2f(20,20);
	data.sightRadius = 256;
	data.followDistance = 512;
	data.minRange = 64;
	data.pauseLength = 2;
	data.pauseTime = 0;

	m_dataTable[ ptr ] = data;
	entList.push_front(ptr);
}

/** Remove an Entity from this brain */
void AI_Brain::RemoveEntity( IEntity *ent )
{
	std::list<IEntity*>::iterator iter;
	for(iter = entList.begin(); iter != entList.end(); ++iter)
        if ( *iter == ent ) { entList.erase( iter ); break; }

	m_dataTable.erase( ent );
}

/** Remove all Entities */
void AI_Brain::RemoveAll()
{
    entList.clear();
    m_dataTable.clear();
}

/** call think on each entity */
void AI_Brain::ControlEntities()
{
	std::list<IEntity*>::iterator iter;
	for(iter = entList.begin(); iter != entList.end(); ++iter)
		Think(*iter);	
}


/** Check to see if this ent is stuck on an obsticle */
void AI_Brain::CheckStuck(IEntity* entPtr, const Vector2f &destination)
{
	AI_Data &data = m_dataTable[entPtr];
    Vector2f vPos = entPtr->m_physObject.GetPosition();
    if( data.lastLocation == vPos && vPos != destination )
	{
		data.path.clear();
		FillPath( entPtr, destination, TILE_SIZE );
	}
}


/** Resume Patrolling - walking around */
void AI_Brain::ContinuePatrol(IEntity* entPtr)
{
	AI_Data &data = m_dataTable[entPtr];

    Vector2f destination = data.patrol[data.patrolIndex];
	WalkTo( entPtr->m_physObject.GetPosition(), destination, entPtr);
	if( CloseEnough(destination, entPtr) )
	{
		data.patrolIndex++;
		if(data.patrolIndex >= data.patrol.size())
			data.patrolIndex = 0;

		WalkTo( entPtr->m_physObject.GetPosition(), data.patrol[data.patrolIndex], entPtr);
	}
}


/** Go to another Entity */
void AI_Brain::GoToEntity(IEntity* entPtr, IEntity* destinationEntity)
{
	MoveTo( entPtr, destinationEntity->m_physObject.GetPosition() );
}

/** Walk to a location */
void AI_Brain::MoveTo(IEntity* entPtr, const Vector2f &vDest)
{
 //   AI_Data &data = m_dataTable[ent];
	//data.path.clear();
	//FillPath(ent, destination, TILE_SIZE );
	AI_Data &data = m_dataTable[entPtr];
	if(data.path.size() < 2)
	{
		data.path.clear();
		FillPath(entPtr, vDest, TILE_SIZE );
	}
	else
	{
		if( !CloseEnough(data.path[1],entPtr) )
		{
			WalkTo(data.path[0],data.path[1],entPtr);
			data.lastLocation = entPtr->m_physObject.GetPosition();
		}
		else
		{
            data.path.pop_front();
			if(data.path.size() > 1)
				WalkTo(data.path[0], data.path[1], entPtr);
			else
			{
				data.path.clear();
				FillPath(entPtr, vDest, TILE_SIZE );
			}	
		}
	}
}

/** Run away from the enemy */
void AI_Brain::RunAway(IEntity* ent, IEntity* enemy)
{
    AI_Data &data = m_dataTable[ent]; 
    const int MAX_RUNDISTANCE = 500;


}


/** Find the closest cover from an enemy */
Vector2f AI_Brain::FindCover(IEntity* entPtr, IEntity* enemy)
{
	AI_Data &data = m_dataTable[entPtr];

    const int MAX_RANGE = 500;
	Vector2f loc = entPtr->m_physObject.GetPosition();
    Vector2f enemyPosition = enemy->m_physObject.GetPosition();

    Vector2f closesPoint = loc;

	Scene* scene = EntityManager::GetInstance()->GetScene();
    if ( scene && !scene->IsLoading() )
    {
        std::map< type_ID, Scene::tile_t* >  tileList;
        QuadTree<Scene::tile_t*>* layerTwo =  scene->GetSecondLayerQuadTree();

        Rect coverBounds( loc.x, loc.y, MAX_RANGE, MAX_RANGE );
        layerTwo->GetObjects( coverBounds, tileList );
        std::map< type_ID, Scene::tile_t* >::iterator it = tileList.begin();

        if ( ! tileList.empty() )
        {
            float closes = MAX_RANGE;
            for(; it != tileList.end(); ++it )
            {
                Vector2f pos = it->second->GetLocation();
                int c = pos.Distance( loc ); // distance from entity
                int d = pos.Distance( enemyPosition ); // distance from enemy
                if ( c < closes && d > 100 )
                {
                    closesPoint = pos;
                    closes = c;
                }

            }
            MoveTo( entPtr, closesPoint );
        }
    }
    return closesPoint;
}


/** Fill this entity's path to a location */
void AI_Brain::FillPath(IEntity* entPtr, const Vector2f &destination, int pixelsAway)
{
	AI_Data &data = m_dataTable[entPtr];

	Vector2f entVector = entPtr->m_physObject.GetPosition();

	PathNode nullNode( Vector2f(-1000,-1000) );
	nullNode.distanceFromGoal = 10000;
	nullNode.isClosed = true;

	//the root is the starting location
	PathNode rootNode( entVector );
	rootNode.entityPath = &nullNode;
	rootNode.distanceFromGoal = 10000;
	rootNode.isClosed = true;

	//the list of nodes waiting to be explored or that have been explored
	std::list<PathNode> openList;
	//put the root in the list of nodes to be explored
	openList.push_front(rootNode);

	//our goal is to reach the player
	PathNode goalNode( destination );
	//we have reached our goal when the location is within pixelsAway from the player
	bool goalReached = !(Distance( goalNode, rootNode) > pixelsAway);

	PathNode* currentNode = &rootNode;
	currentNode->distanceFromGoal = 10000;
	currentNode->isClosed = true;

    std::list<PathNode>::iterator iter;
	//explore until the goal is reached
	while(!goalReached)
	{
		//find the node from the open list that is closest to the player
		for(iter = openList.begin(); iter != openList.end(); ++iter)
		{
			if( !iter->isClosed )
				if( currentNode->distanceFromGoal >= iter->distanceFromGoal )
					currentNode = &(*iter);	
		}
		
		//add the neighbors of the current node into the open list
		AddNeighbors(openList, *currentNode, entPtr, destination );
		
		//add the current node into the closed list so we dont visit it again
		currentNode->isClosed = true;
		
		//decide if we have reached our goal
		goalReached = !(currentNode->distanceFromGoal > pixelsAway);
		
		if(!goalReached)
			currentNode = &rootNode;

	}
	
	while(currentNode->location.x != -1000)
	{
        data.path.push_front( currentNode->location );
		currentNode = currentNode->entityPath;
	}
	
}


/** Walk to a desired location */
void AI_Brain::WalkTo(const Vector2f &current, const Vector2f &destination, IEntity* entPtr)
{

	AI_Data &data = m_dataTable[entPtr];

	int button = 0;

	bool alignedX = false;
	bool alignedY = false;

	if(current.Distance(destination) > 90)
	{
		data.path.clear();
		FillPath(entPtr, destination, TILE_SIZE);
	}

	alignedX = abs(current.x - destination.x) < 4;
	alignedY = abs(current.y - destination.y) < 4;

	if(!alignedX)
	    if(current.x > destination.x)
			button |= BUTTON_LEFT;
		else
			button |= BUTTON_RIGHT;
	
	if(!alignedY)
		if(current.y < destination.y)
			button |= BUTTON_DOWN;
		else
			button |= BUTTON_UP;

	if(alignedX && alignedY)
		button |= BUTTON_ATTACK1;

	entPtr->m_actions = button;

}

